package client

import (
	"errors"
	"fmt"
	"learning/gooop/etcd/raft/debug"
	"learning/gooop/etcd/raft/logger"
	"learning/gooop/etcd/raft/model"
	rrpc "learning/gooop/etcd/raft/rpc"
	"learning/gooop/etcd/raft/timeout"
	"net/rpc"
	"sync"
	"time"
)

type tBrokenState struct {
	model.TEventDrivenModel
	context iStateContext

	mInitOnce  sync.Once
	mStartOnce sync.Once

	mDisposedFlag bool
}

// trigger : init()
// args: empty
const beInit = "broken.init"

// trigger: Start()
// args: empty
const beStart = "broken.Start"

// trigger: whenStartThenBeginDial
// args: *rpc.Client
const beDialOK = "broken.DialOK"

// trigger: whenDialOKThenSwitchToConnectedState
// args: empty
const beDisposing = "broken.disposing"

func newBrokenState(ctx iStateContext) iClientState {
	it := new(tBrokenState)
	it.init(ctx)
	return it
}

func (me *tBrokenState) init(ctx iStateContext) {
	me.mInitOnce.Do(func() {
		me.context = ctx
		me.initEventHandlers()
		me.Raise(beInit)
	})
}

func (me *tBrokenState) initEventHandlers() {
	// write only logic
	me.hookEventsForDisposedFlag()

	// read only logic
	me.Hook(beStart,
		me.whenStartThenBeginDial)

	me.Hook(beDialOK,
		me.whenDialOKThenSetConn)

	me.Hook(beDialOK,
		me.whenDialOKThenSwitchToConnectedState)
}

func (me *tBrokenState) hookEventsForDisposedFlag() {
	me.Hook(beInit, func(e string, args ...interface{}) {
		me.mDisposedFlag = false
	})

	me.Hook(beDisposing, func(e string, args ...interface{}) {
		me.mDisposedFlag = true
		me.logf(".beDisposing")
	})
}

func (me *tBrokenState) Start() {
	me.mStartOnce.Do(func() {
		me.Raise(beStart)
	})
}

func (me *tBrokenState) whenStartThenBeginDial(_ string, _ ...interface{}) {
	me.logf(".whenStartThenBeginDial")
	go func() {
		time.Sleep(timeout.HeartbeatInterval)

		for !me.mDisposedFlag && debug.KilledNodeID != me.context.Config().GetID() {
			conn, err := rpc.Dial("tcp", me.context.Peer().GetEndpoint())
			if err == nil {
				me.Raise(beDialOK, conn)
				break

			} else {
				time.Sleep(timeout.ClientRedialInterval)
			}
		}
	}()
}

func (me *tBrokenState) whenDialOKThenSetConn(_ string, args ...interface{}) {
	me.logf(".whenDialOKThenSetConn")

	conn := args[0].(*rpc.Client)
	me.context.SetConn(conn)
}

func (me *tBrokenState) whenDialOKThenSwitchToConnectedState(_ string, _ ...interface{}) {
	me.logf(".whenDialOKThenSwitchToConnectedState")

	me.Raise(beDisposing)
	me.context.HandleStateChanged(newConnectedState(me.context))
}

func (me *tBrokenState) Heartbeat(cmd *rrpc.HeartbeatCmd, ret *rrpc.HeartbeatRet) error {
	return gErrorConnectionBroken
}

func (me *tBrokenState) AppendLog(cmd *rrpc.AppendLogCmd, ret *rrpc.AppendLogRet) error {
	ret.Code = rrpc.ALInternalError
	return gErrorConnectionBroken
}

func (me *tBrokenState) CommitLog(cmd *rrpc.CommitLogCmd, ret *rrpc.CommitLogRet) error {
	ret.Code = rrpc.CLInternalError
	return gErrorConnectionBroken
}

func (me *tBrokenState) RequestVote(cmd *rrpc.RequestVoteCmd, ret *rrpc.RequestVoteRet) error {
	return gErrorConnectionBroken
}

func (me *tBrokenState) Ping(cmd *rrpc.PingCmd, ret *rrpc.PingRet) error {
	return gErrorConnectionBroken
}

func (me *tBrokenState) ExecuteKVCmd(cmd *rrpc.KVCmd, ret *rrpc.KVRet) error {
	ret.Code = rrpc.KVInternalError
	return gErrorConnectionBroken
}

func (me *tBrokenState) logf(format string, args ...interface{}) {
	prefix := fmt.Sprintf("tBrokenState(from=%v, to=%v@%v)",
		me.context.Config().GetID(),
		me.context.Peer().GetID(),
		me.context.Peer().GetEndpoint())
	logger.Logf(prefix+format, args...)
}

var gErrorConnectionBroken = errors.New("peer connection broken")
